using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace Pharmacy.Ultilities
{
    /// <summary>
    /// Provides helper for exception throwing and handling.
    /// <code>
    /// Usage:
    ///     try
    ///     {
    ///         // ...
    ///     }
    ///     catch (NullReferenceException ex)
    ///     {
    ///         throw ExceptionHelper
    ///             .From(ex)
    ///             .Message("Cannot find the single node in server configuration.")
    ///             .Data("nodeName", nodeName)
    ///             .Data("attribute", attribute)
    ///             .DataRange("list", list)
    ///             .ToException();
    ///     }
    /// </code>
    /// </summary>
    public class ExceptionHelper
    {
        /// <summary>
        /// Gets or sets the inner exception.
        /// </summary>
        private readonly Exception _innerException;

        /// <summary>
        /// Gets or sets the custom message.
        /// </summary>
        private string _message;

        /// <summary>
        /// Gets or sets the debug data.
        /// </summary>
        private readonly Dictionary<string, object> _data = new Dictionary<string, object>();

        #region Static methods

        /// <summary>
        /// Initiaiizes new Exception Helper by given exception.
        /// </summary>
        /// <param name="ex">Thrown exception.</param>
        /// <returns></returns>
        public static ExceptionHelper From(Exception ex)
        {
            return new ExceptionHelper(ex);
        }

        #endregion

        /// <summary>
        /// Initializes new instance of <see cref="ExceptionHelper"/>.
        /// </summary>
        /// <param name="ex"></param>
        public ExceptionHelper(Exception ex)
        {
            this._innerException = ex;
        }

        /// <summary>
        /// Sets custom message to the exception.
        /// </summary>
        /// <param name="message">Custom message.</param>
        /// <returns></returns>
        public ExceptionHelper Message(string message)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(!string.IsNullOrEmpty(message));
            // --- End parameters checking code -------------------------------

            this._message = message;

            return this;
        }

        /// <summary>
        /// Sets custom message to the exception.
        /// </summary>
        /// <param name="message">Unformatted custom message.</param>
        /// <param name="args">Format arguments</param>
        /// <returns></returns>
        public ExceptionHelper Message(string message, params object[] args)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(!string.IsNullOrEmpty(message));
            // --- End parameters checking code -------------------------------

            this._message = string.Format(message, args);

            return this;
        }

        /// <summary>
        /// Adds debug data into the exception.
        /// </summary>
        /// <param name="key">Key of the added data.</param>
        /// <param name="value">Value of the added data.</param>
        /// <returns></returns>
        public ExceptionHelper Data(string key, object value)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(!string.IsNullOrEmpty(key));
            // --- End parameters checking code -------------------------------

            this._data.Add(key, value);

            return this;
        }

        /// <summary>
        /// Adds a collection of debug data into the exception.
        /// </summary>
        /// <param name="key">Key of the added data.</param>
        /// <param name="values">Collection of values of the added data.</param>
        /// <returns></returns>
        public ExceptionHelper DataRange(string key, IEnumerable values)
        {
            int i = 0;
            foreach (object value in values)
            {
                this._data.Add(string.Format("{0}`[{1}]", key, i++), value);
            }

            return this;
        }

        /// <summary>
        /// Returns the exception.
        /// </summary>
        /// <returns></returns>
        public Exception ToException()
        {
            // prepare result exception
            Exception ret = string.IsNullOrEmpty(this._message)
                                ? this._innerException
                                : new ApplicationException(this._message, this._innerException);

            // add debug data to the exception
            foreach (KeyValuePair<string, object> data in _data)
            {
                ret.Data.Add(data.Key, data.Value);
            }

            return ret;
        }

        /// <summary>
        /// Shows the exception to the user.
        /// </summary>
        public void Show()
        {
            //NotifyUserUtil.ShowException(this.ToException());
        }

        #region Static methods

        /// <summary>
        /// Converts this exception helper to actual exception.
        /// </summary>
        /// <param name="exceptionHelper">Reference to the exception to be converted.</param>
        /// <returns></returns>
        public static implicit operator Exception(ExceptionHelper exceptionHelper)
        {
            // --- Begin parameters checking code -----------------------------
            Debug.Assert(exceptionHelper != null);
            // --- End parameters checking code -------------------------------

            return exceptionHelper.ToException();
        }

        #endregion
    }
}